
package com.cosc645.ecash;

import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;

public class Alice {
    private MoneyOrder bankSignedMoneyOrder;
    //TODO: create id with name address etc
    private String name = "ALICE";
    private String address = "123 Main St, Baltimore, MD, 21212";
    private String specialID = "alicespecialID";
    private String L0;
    private String R0;
    private String L0hash;
    private String R0hash;
    public String commitmentHash;
    private RSAPrivateKey privateKey;
    public RSAPublicKey publicKey;

    private List<BigInteger> blindedMoneyOrders;
    private int lastUsed = 100;
    String idKey;
    String xoredS;

    BigInteger randomB = null;

    //MoneyOrder[] moneyOrderArray = new MoneyOrder[100];
    PriorityQueue<MoneyOrder> moneyOrdersQueue;

    public Alice() {
        KeyPairGenerator keyGen = null;
        try {
            keyGen = KeyPairGenerator.getInstance("RSA");
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        keyGen.initialize(2048, new SecureRandom());
        KeyPair keypair = keyGen.genKeyPair();
        privateKey = (RSAPrivateKey) keypair.getPrivate();
        publicKey = (RSAPublicKey) keypair.getPublic();
    }

    /**
     * @param amount
     */
    public void createMoneyOrder(double amount) {
        //
        MoneyOrderComparator comp = new MoneyOrderComparator();
        moneyOrdersQueue = new PriorityQueue(TestDriver.MONEY_ORDER_SIZE, comp);
        for (int i = 0; i < TestDriver.MONEY_ORDER_SIZE; i++) {
            identityEncoding();
            MoneyOrder currentMoneyOrder = new MoneyOrder(amount);
            currentMoneyOrder.setL0(xoredS);
            currentMoneyOrder.setR0(idKey);
            moneyOrdersQueue.add(currentMoneyOrder);
        }

    }

    public MoneyOrder getBankSignedMoneyOrder() {
        return bankSignedMoneyOrder;
    }

    public void setBankSignedMoneyOrder(MoneyOrder signed) {
        bankSignedMoneyOrder = signed;
    }

    /**
     * getSpecialIdentityString() hashes L0 and R0 and returns it.
     *
     * @return L0hash and R0hash with : in between them
     */
    public String getSpecialIdentityString() {
        L0hash = hash(L0);
        R0hash = hash(R0);
        return L0hash + ":" + R0hash;
    }

    public void setID(String ident) {
        specialID = ident;
    }

    public void makeEnvelope(RSAPublicKey pubKey) {
        byte[] bytes1 = new byte[TestDriver.MONEY_ORDER_SIZE];
        SecureRandom secureRandom = null;
        try {
            secureRandom = SecureRandom.getInstance("SHA1PRNG");
        } catch (NoSuchAlgorithmException e1) {
            e1.printStackTrace();
        }


        randomB = new BigInteger("8191");
        System.out.println("randomB: " + randomB);

        MoneyOrderComparator comp = new MoneyOrderComparator();
        PriorityQueue tempPriorityQueue = new PriorityQueue(TestDriver.MONEY_ORDER_SIZE, comp);
        blindedMoneyOrders = new ArrayList<BigInteger>();

        for (int i = 0; i < TestDriver.MONEY_ORDER_SIZE; i++) {
            
            MoneyOrder currentMoneyOrder = moneyOrdersQueue.remove();            

            System.out.println("ToString: " + currentMoneyOrder.toString());
            byte[] bytes = null;
            try {
                bytes = currentMoneyOrder.toString().getBytes("UTF-8");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }

            BigInteger m = new BigInteger(bytes);


            System.out.println("m: " + m);
            BigInteger blind = blind(pubKey, m);

            blindedMoneyOrders.add(blind);

            tempPriorityQueue.add(currentMoneyOrder);

        }
        this.moneyOrdersQueue = tempPriorityQueue;
        
       
    }

    /**
     * Alice blinding the money order.
     * s = m * b ^ e(mod n)
     *
     * @param pubKey - bank's public key
     * @param m
     * @return
     */
    private BigInteger blind(RSAPublicKey pubKey, BigInteger m) {
        BigInteger e = pubKey.getPublicExponent();
        BigInteger n = pubKey.getModulus();
        BigInteger pdfS = ((randomB.modPow(e, n)).multiply(m)).mod(n);
        System.out.println("BLIND: " + pdfS);
        return pdfS;
    }

    /**
     * Alice unblinds the money order still signed by the bank.
     * r = t * b ^-1(mod n)
     *
     * @param tFromBank
     * @return
     */
    public BigInteger unblind(RSAPublicKey pubKey, BigInteger tFromBank) {
        BigInteger n = pubKey.getModulus();
        BigInteger e = pubKey.getPublicExponent();
        BigInteger r = randomB.modInverse(n).multiply(tFromBank).mod(n);
        
         
        
       // BigInteger test = r.modPow(e , n);
           System.out.println("r decrypted: "+ r);
        return r;
    }

    /**
     * Unblind to original money order
     * @param publicKey
     * @param pdfS
     * @return
     */
    public BigInteger unblindUnOfficial(RSAPublicKey publicKey, BigInteger pdfS) {
        BigInteger e = publicKey.getPublicExponent();
        BigInteger n = publicKey.getModulus();
        return (((randomB.modPow(e, n)).modInverse(n)).multiply(pdfS)).mod(n);
    }

    public void identityEncoding() {
        String IDstring = specialID + name + address + lastUsed;
        lastUsed++;
        idKey = "idKey";
        StringXorer sor = new StringXorer();

        //xoredS = xorStrings(IDstring, idKey);
        xoredS = sor.encode(IDstring, idKey);

        System.out.println("XORed String: " + xoredS);

        //String test = xorStrings(xoredS, idKey);
        String test = sor.decode(xoredS, idKey);
        System.out.println("UNXORed String: " + test);

    }

    public void commit() {
        String P;
        String b;

        b = "alice";
        P = "alicescommitP";
        commitmentHash = hash(P + b);


    }


    private String hash(String str) {
        String md5 = null;

        try {

            //Create MessageDigest object for MD5
            MessageDigest digest = MessageDigest.getInstance("MD5");

            //Update input string in message digest
            digest.update(str.getBytes(), 0, str.length());

            //Converts message digest value in base 16 (hex)
            md5 = new BigInteger(1, digest.digest()).toString(16);

        } catch (NoSuchAlgorithmException e) {

            e.printStackTrace();
        }
        return md5;
    }

    public String getL0() {
        return L0;
    }

    public String getR0() {
        return R0;
    }


    private String pad(String str, int n) {
        if (str.length() % n != 0) {
            for (int i = str.length() % n; i < n; i++) {
                System.out.println("CONCATINGGGGGGGGGGGG");
                str = str.concat("0");
            }
        }
        return str;
    }

    public PriorityQueue getMoneyOrdersQueue() {
        return moneyOrdersQueue;
    }

    public void commitMoneyOrder() {
        MoneyOrderComparator comp = new MoneyOrderComparator();
        PriorityQueue<MoneyOrder> priorityQueue = new PriorityQueue(TestDriver.MONEY_ORDER_SIZE, comp);
        for (int i = 0; i < TestDriver.MONEY_ORDER_SIZE; i++) {
            priorityQueue.add(moneyOrdersQueue.remove());
        }
        MoneyOrder moneyOrder = priorityQueue.remove();
        moneyOrder.addCommitment(getL0(), getR0());
        priorityQueue.add(moneyOrder);

        moneyOrdersQueue = priorityQueue;

    }

    public void setMoneyOrdersQueue(PriorityQueue priorityQueue) {
        moneyOrdersQueue = priorityQueue;
    }


    public List<BigInteger> getBlindedMoneyOrders() {
        return blindedMoneyOrders;
    }

    public void setBlindedMoneyOrders(List<BigInteger> blindedMoneyOrders) {
        this.blindedMoneyOrders = blindedMoneyOrders;
    }

    private String xorStrings(String IDstring, String idKey) {
        //String s = IDstring ^ idKey;
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < IDstring.length(); i++) {
            sb.append((char) (IDstring.charAt(i) ^ idKey.charAt(i % idKey.length())));
        }
        String result = sb.toString();

        return result;
        
        /*
        //TODO: randomize n
        int n = 4;
        String IDstring = specialID + name + address;


        L0 = IDstring.substring(0, IDstring.length() / 2);
        R0 = IDstring.substring(IDstring.length() / 2, IDstring.length());
        System.out.println("L0 = " + L0);
        System.out.println("R0 = " + R0);

        int split = L0.length() / n;
        System.out.println("split = " + split);

        L0 = pad(L0, n);
        R0 = pad(R0, n);

        String left[] = new String[L0.length() / n];
        String right[] = new String[R0.length() / n];

        for (int i = 0; i < L0.length() / n; i++) {
            left[i] = L0.substring(i * 4, (i * 4) + 4);
            right[i] = R0.substring(i * 4, (i * 4) + 4);

            //System.out.println("left[] = "+left[i]);
            System.out.println("right[] = " + right[i]);
        }
        */
    }    

    public BigInteger computeR(BigInteger t, RSAPublicKey publicKey) {
        return t.multiply(randomB).modInverse(publicKey.getModulus());
    }

    public BigInteger verifyR(RSAPublicKey pubkey, BigInteger t)
    {
        BigInteger verifyR = t.multiply(randomB).modInverse(pubkey.getModulus());
        return verifyR;
    }
}
